import akshare as ak
import numpy as np
import pandas as pd
import time

def get_combined_fund_data(symbol):
    """
    获取基金的单位净值和累计净值数据，并进行合并处理
    
    本函数从东方财富网获取指定基金的历史净值数据，包括单位净值和累计净值，
    并将两个数据集合并、清洗、标记和排序，为后续的收益率和回撤计算提供基础数据。
    
    参数:
        symbol (str): 基金代码，例如 "710001"
    
    返回:
        pandas.DataFrame: 合并处理后的数据框，包含净值日期、单位净值、累计净值、净值是否相等等列
        None: 如果数据获取失败则返回None
    
    功能说明:
        1. 使用重试机制获取基金的单位净值走势数据
        2. 使用重试机制获取基金的累计净值走势数据
        3. 按净值日期合并两个数据集（内连接）
        4. 标记单位净值与累计净值是否相等
        5. 按日期降序排列（最新日期在前）
        
    数据来源:
        akshare库的fund_open_fund_info_em接口，数据来自东方财富网
    """
    
    # ==================== 第一步：获取单位净值数据 ====================
    
    # 配置重试参数
    # 由于网络请求可能不稳定，设置重试机制提高数据获取成功率
    max_retries = 3  # 最大重试次数
    retry_delay = 2  # 重试间隔（秒）
    
    # 初始化单位净值数据变量
    fund_nav_data = None
    
    # 使用重试循环获取单位净值数据
    # attempt从0到max_retries-1，共尝试max_retries次
    for attempt in range(max_retries):
        try:
            # 调用akshare接口获取单位净值走势数据
            # indicator="单位净值走势"指定获取单位净值相关数据
            # 返回的DataFrame包含：净值日期、单位净值、日增长率
            fund_nav_data = ak.fund_open_fund_info_em(symbol=symbol, indicator="单位净值走势")
            
            # 检查数据是否成功获取
            if fund_nav_data is not None and not fund_nav_data.empty:
                break  # 成功获取数据，跳出重试循环
            else:
                # 数据为空，打印警告信息
                print(f"第 {attempt + 1} 次尝试：未能获取基金 {symbol} 的单位净值数据")
                
        except Exception as e:
            # 捕获网络请求异常、API异常等所有异常
            print(f"第 {attempt + 1} 次尝试失败：获取单位净值数据异常 - {e}")
        
        # 如果不是最后一次尝试，等待一段时间后重试
        if attempt < max_retries - 1:
            print(f"等待 {retry_delay} 秒后重试...")
            time.sleep(retry_delay)  # 暂停指定秒数
    
    # 验证最终是否成功获取数据
    if fund_nav_data is None or fund_nav_data.empty:
        print(f"错误：经过 {max_retries} 次尝试后仍未能获取基金 {symbol} 的单位净值数据")
        return None  # 获取失败，返回None
    
    # 数据清洗：删除日增长率列
    # 原因：我们将使用累计净值来计算收益率，不需要日增长率
    fund_nav_data = fund_nav_data.drop(columns=['日增长率'])
    
    # ==================== 第二步：获取累计净值数据 ====================
    
    # 初始化累计净值数据变量
    fund_cumulative_nav_data = None
    
    # 使用重试循环获取累计净值数据
    for attempt in range(max_retries):
        try:
            # 调用akshare接口获取累计净值走势数据
            # indicator="累计净值走势"指定获取累计净值相关数据
            # 返回的DataFrame包含：净值日期、累计净值
            fund_cumulative_nav_data = ak.fund_open_fund_info_em(symbol=symbol, indicator="累计净值走势")
            
            # 检查数据是否成功获取
            if fund_cumulative_nav_data is not None and not fund_cumulative_nav_data.empty:
                break  # 成功获取数据，跳出重试循环
            else:
                # 数据为空，打印警告信息
                print(f"第 {attempt + 1} 次尝试：未能获取基金 {symbol} 的累计净值数据")
                
        except Exception as e:
            # 捕获网络请求异常、API异常等所有异常
            print(f"第 {attempt + 1} 次尝试失败：获取累计净值数据异常 - {e}")
        
        # 如果不是最后一次尝试，等待一段时间后重试
        if attempt < max_retries - 1:
            print(f"等待 {retry_delay} 秒后重试...")
            time.sleep(retry_delay)  # 暂停指定秒数
    
    # 验证最终是否成功获取数据
    if fund_cumulative_nav_data is None or fund_cumulative_nav_data.empty:
        print(f"错误：经过 {max_retries} 次尝试后仍未能获取基金 {symbol} 的累计净值数据")
        return None  # 获取失败，返回None
    
    # ==================== 第三步：合并两个数据集 ====================
    
    # 使用pandas的merge方法合并两个数据框
    # on='净值日期'：以"净值日期"作为合并的键（公共列）
    # how='inner'：内连接，只保留两个数据集中都存在的日期
    # 这样可以确保每一行都同时有单位净值和累计净值数据
    combined_data = fund_nav_data.merge(fund_cumulative_nav_data, on='净值日期', how='inner')
    
    # ==================== 第四步：数据标记 ====================
    
    # 添加新列"净值是否相等"，用于标记单位净值与累计净值是否不相等
    # 对于有分红的基金，累计净值会大于单位净值
    # True表示不相等（有分红），False表示相等（无分红）
    combined_data['净值是否异常'] = combined_data['单位净值'] != combined_data['累计净值']
    
    # ==================== 第五步：数据排序 ====================
    
    # 按净值日期进行降序排序，使最新日期排在最前面
    # ascending=False：降序排列（从新到旧）
    # reset_index(drop=True)：重置索引，使索引从0开始连续编号，drop=True表示不保留原索引
    # 这样排序后，索引0对应最新日期，便于后续的滑动窗口计算
    combined_data = combined_data.sort_values(by='净值日期', ascending=False).reset_index(drop=True)
    
    # ==================== 第六步：返回结果 ====================
    
    # 返回合并、清洗、标记和排序后的数据框
    return combined_data

def calculate_period_returns(df):
    """
    计算不同持有期的收益率
    
    收益率是衡量投资回报的核心指标，表示在特定持有期内，投资的增值百分比。
    本函数计算基金在不同时间窗口下的持有收益率。
    
    参数:
        df (pandas.DataFrame): 包含净值数据的数据框，必须包含'累计净值'和'单位净值'列
    
    返回:
        pandas.DataFrame: 添加了各期收益率列的数据框，如果出错则返回None
        
    功能说明:
        1. 对每个时间窗口（1周、1月、3月等）计算持有收益率
        2. 使用滑动窗口方法，为每一行数据计算其对应窗口期的收益率
        3. 数据按降序排列（最新日期在前），往后推算历史数据
        4. 对于历史数据不足的行，填充NaN
        
    计算公式:
        收益率(%) = (当前累计净值 - 历史累计净值) / 历史单位净值 × 100
    """
    
    try:
        # ==================== 第一步：输入数据验证 ====================
        
        # 检查DataFrame是否为空或None
        if df is None or df.empty:
            print("错误：输入数据为空")
            return None
        
        # 检查必要的列是否存在
        # '累计净值'：用于计算净值变化量
        # '单位净值'：用作收益率计算的分母基准
        required_columns = ['累计净值', '单位净值']
        for col in required_columns:
            if col not in df.columns:
                print(f"错误：数据中缺少必要的列 '{col}'")
                return None
        
        # 创建数据框的副本，避免修改原始数据
        result_df = df.copy()

        # ==================== 第二步：定义时间窗口 ====================
        
        # 定义不同持有期对应的交易日天数
        # 注意：这里的天数是交易日，不是自然日
        # 例如：1周 ≈ 5个交易日，1月 ≈ 22个交易日
        periods = {
            '持有1周收益率(%)': 5,      # 5个交易日
            '持有1月收益率(%)': 22,     # 约1个月的交易日
            '持有3月收益率(%)': 66,     # 约3个月的交易日
            '持有半年收益率(%)': 132,   # 约半年的交易日
            '持有1年收益率(%)': 242,    # 约1年的交易日
            '持有2年收益率(%)': 484,    # 约2年的交易日
            '持有3年收益率(%)': 726     # 约3年的交易日
        }

        # ==================== 第三步：计算各期收益率 ====================
        
        # 遍历每个时间窗口
        for period_name, days in periods.items():
            # 初始化当前窗口期的收益率列表
            returns = []
            
            # 遍历每一行数据（每个交易日）
            # i 表示当前日期在数据框中的索引位置
            for i in range(len(result_df)):

                try:
                    # ---------- 步骤3.1：检查历史数据是否充足 ----------
                    
                    # 判断从当前位置往后是否有足够的历史数据
                    # 因为数据是降序排列（新→旧），i+days表示往后推days天
                    # 如果超出数据框长度，说明历史数据不足
                    if i + days >= len(result_df):
                        returns.append(np.nan)  # 数据不足，填充NaN
                        continue  # 跳过当前循环，处理下一行

                    # ---------- 步骤3.2：提取当前和历史净值数据 ----------
                    
                    # 获取当前日期的累计净值（用于计算收益的终点值）
                    current_accumulated_nav = result_df.iloc[i]['累计净值']
                    
                    # 计算历史日期的索引位置
                    # i + days - 1：从当前位置往后推days天，减1是因为包含当前天
                    # 例如：i=0, days=5，则historical_index=4，表示第5个交易日
                    historical_index = i + days - 1
                    
                    # 获取历史日期的累计净值（用于计算收益的起点值）
                    historical_accumulated_nav = result_df.iloc[historical_index]['累计净值']
                    
                    # 获取历史日期的单位净值（用作收益率计算的基准）
                    historical_unit_nav = result_df.iloc[historical_index]['单位净值']

                    # ---------- 步骤3.3：数据有效性检查 ----------
                    
                    # 检查关键数据是否有效：
                    # 1. 当前累计净值不能为NaN
                    # 2. 历史累计净值不能为NaN
                    # 3. 历史单位净值不能为NaN
                    # 4. 历史单位净值不能为0（避免除零错误）
                    if (pd.isna(current_accumulated_nav) or 
                        pd.isna(historical_accumulated_nav) or 
                        pd.isna(historical_unit_nav) or 
                        historical_unit_nav == 0):
                        returns.append(np.nan)  # 数据无效，填充NaN
                        continue  # 跳过当前循环，处理下一行
            
                    # ---------- 步骤3.4：计算收益率 ----------
                    
                    # 计算净值变化量 = 当前累计净值 - 历史累计净值
                    # 这个差值表示持有期内基金净值的增长（或下降）
                    nav_change = current_accumulated_nav - historical_accumulated_nav
                    
                    # 计算收益率百分比
                    # 公式：收益率(%) = (净值变化量 / 历史单位净值) × 100
                    # 保留2位小数，便于阅读
                    period_return = round((nav_change / historical_unit_nav) * 100, 2)
                    
                    # 将计算结果添加到列表中
                    returns.append(period_return)
                    
                except (IndexError, KeyError, TypeError, ZeroDivisionError) as e:
                    # ---------- 异常处理：常见错误 ----------
                    
                    # IndexError: 数据框索引越界
                    # KeyError: 列名不存在
                    # TypeError: 类型错误（如对None进行数值运算）
                    # ZeroDivisionError: 除零错误（虽然前面已检查，但双重保险）
                    print(f"警告：计算第 {i} 行 {period_name} 时出错: {e}")
                    returns.append(np.nan)  # 出错时填充NaN
                    
                except Exception as e:
                    # ---------- 异常处理：未预期的错误 ----------
                    
                    # 捕获所有其他类型的异常，防止程序崩溃
                    print(f"警告：计算第 {i} 行 {period_name} 时发生未知错误: {e}")
                    returns.append(np.nan)

            # ---------- 步骤3.5：批量赋值 ----------
            
            # 将计算完成的收益率列表赋值给DataFrame的新列
            result_df[period_name] = returns

        # ==================== 第四步：返回结果 ====================
        
        # 返回添加了收益率列的DataFrame
        return result_df
        
    except Exception as e:
        # ==================== 函数级异常处理 ====================
        
        # 捕获整个函数执行过程中的任何异常
        # 这是最后一道防线，确保函数不会因未处理的异常而崩溃
        print(f"错误：计算收益率时发生异常 - {e}")
        return None


def get_max_withdraw(indexs):
    """
    计算投资序列的最大回撤（Maximum Drawdown）

    最大回撤：从最高点到最低点的最大跌幅，衡量投资可能面临的最大损失

    参数:
        indexs: 数字列表，代表基金净值或价格序列

    返回:
        (最大回撤百分比, 峰值位置索引, 谷值位置索引)
    """
    # 初始化变量
    max_withdraw = 0          # 当前找到的最大回撤值（小数形式）
    peak_index = 0            # 最大回撤对应的峰值位置（索引）
    valley_index = 0          # 最大回撤对应的谷值位置（索引）
    last_high = indexs[0]     # 记录到目前为止的最高点，初始为第一个值
    last_high_index = 0       # 记录最高点出现的位置

    # 遍历整个数据序列
    for index, current in enumerate(indexs):
        # 如果当前值超过了之前的最高点，更新最高点
        if current > last_high:
            last_high = current
            last_high_index = index
        else:
            # 计算从最高点到当前点的回撤幅度
            # 回撤 = (最高点 - 当前点) / 最高点
            current_withdraw = (last_high - current) / last_high

            # 如果当前回撤比之前记录的最大回撤还要大，更新最大回撤
            if current_withdraw > max_withdraw:
                max_withdraw = current_withdraw
                peak_index = last_high_index    # 记录峰值位置
                valley_index = index            # 记录谷值位置

    # print(f"最大回撤对应的峰值位置: {peak_index}")
    # print(f"最大回撤对应的谷值位置: {valley_index}")
    # print(f"最大回撤: {round(max_withdraw * 100, 2)}")
    
    # 返回结果：将小数形式的回撤转换为百分比（保留2位小数），并返回峰值和谷值位置
    return round(max_withdraw * 100, 2), peak_index, valley_index

def calculate_max_drawdown(df):
    """
    计算不同持有期的最大回撤
    
    最大回撤（Maximum Drawdown）是衡量投资风险的重要指标，表示在特定时间窗口内，
    从最高点到最低点的最大跌幅百分比。
    
    参数:
        df (pandas.DataFrame): 包含净值数据的数据框，必须包含'单位净值'列
    
    返回:
        pandas.DataFrame: 添加了各期最大回撤列的数据框，如果出错则返回None
        
    功能说明:
        1. 对每个时间窗口（1周、1月、3月等）计算最大回撤
        2. 使用滑动窗口方法，为每一行数据计算其对应窗口期的最大回撤
        3. 数据按降序排列（最新日期在前），需要反转后再计算
        4. 对于历史数据不足的行，填充NaN
    """
    
    try:
        # ==================== 第一步：输入数据验证 ====================
        
        # 检查DataFrame是否为空或None
        if df is None or df.empty:
            print("错误：输入数据为空")
            return None
        
        # 检查必要的列'单位净值'是否存在
        # 单位净值是计算回撤的基础数据
        if '单位净值' not in df.columns:
            print("错误：数据中缺少必要的列 '单位净值'")
            return None
        
        # 创建数据框的副本，避免修改原始数据
        result_df = df.copy()
        
        # 将单位净值列转换为NumPy数组，提高计算效率
        nav_values = result_df['单位净值'].values
        
        # 检查净值数组是否为空
        if len(nav_values) == 0:
            print("错误：单位净值数据为空")
            return None

        # ==================== 第二步：定义时间窗口 ====================
        
        # 定义不同持有期对应的交易日天数
        # 注意：这里的天数是交易日，不是自然日
        # 例如：1周 ≈ 5个交易日，1月 ≈ 22个交易日
        periods = {
            '持有1周最大回撤(%)': 5,      # 5个交易日
            '持有1月最大回撤(%)': 22,     # 约1个月的交易日
            '持有3月最大回撤(%)': 66,     # 约3个月的交易日
            '持有半年最大回撤(%)': 132,   # 约半年的交易日
            '持有1年最大回撤(%)': 242,    # 约1年的交易日
            '持有2年最大回撤(%)': 484,    # 约2年的交易日
            '持有3年最大回撤(%)': 726     # 约3年的交易日
        }

        # ==================== 第三步：计算各期最大回撤 ====================
        
        # 遍历每个时间窗口
        for period_name, days in periods.items():
            # 初始化当前窗口期的最大回撤列表
            max_drawdowns = []

            # 遍历每一行数据（每个交易日）
            # i 表示当前日期在数组中的索引位置
            for i in range(len(nav_values)):

                try:
                    # ---------- 步骤3.1：检查历史数据是否充足 ----------
                    
                    # 判断从当前位置往后是否有足够的历史数据
                    # 因为数据是降序排列（新→旧），i+days表示往后推days天
                    # 如果超出数组长度，说明历史数据不足
                    if i + days >= len(nav_values):
                        max_drawdowns.append(np.nan)  # 数据不足，填充NaN
                        continue  # 跳过当前循环，处理下一行
                    
                    # ---------- 步骤3.2：提取窗口期数据 ----------
                    
                    # 获取窗口期内的净值数据
                    # nav_values[i:i+days] 表示从索引i开始，取days个元素
                    # 例如：如果i=0, days=5，则取索引0,1,2,3,4的数据
                    window_nav = nav_values[i:i+days]
                    
                    # ---------- 步骤3.3：数据有效性检查 ----------
                    
                    # 检查窗口数据是否为空（理论上不应该发生，但为了安全起见）
                    if len(window_nav) == 0:
                        max_drawdowns.append(np.nan)
                        continue
                    
                    # 检查窗口数据中是否包含NaN值
                    # 如果有缺失数据，无法准确计算回撤
                    if np.any(np.isnan(window_nav)):
                        max_drawdowns.append(np.nan)
                        continue

                    # ---------- 步骤3.4：数据反转 ----------
                    
                    # 反转数据顺序：从降序（新→旧）转为升序（旧→新）
                    # 原因：最大回撤算法需要按时间顺序（从旧到新）计算
                    # [::-1] 是Python切片语法，表示反转数组
                    reversed_data = window_nav[::-1]
          
                    # ---------- 步骤3.5：计算最大回撤 ----------
                    
                    # 调用get_max_withdraw函数计算最大回撤
                    # 返回值：(最大回撤百分比, 峰值索引, 谷值索引)
                    # 我们只需要最大回撤百分比，所以用 _ 忽略后两个返回值
                    max_withdraw, _, _ = get_max_withdraw(reversed_data)
                    
                    # 将计算结果添加到列表中
                    max_drawdowns.append(max_withdraw)
                    
                except (IndexError, ValueError, TypeError) as e:
                    # ---------- 异常处理：常见错误 ----------
                    
                    # IndexError: 数组索引越界
                    # ValueError: 值错误（如无效的数值运算）
                    # TypeError: 类型错误（如对None进行数值运算）
                    print(f"警告：计算第 {i} 行 {period_name} 时出错: {e}")
                    max_drawdowns.append(np.nan)  # 出错时填充NaN
                    
                except Exception as e:
                    # ---------- 异常处理：未预期的错误 ----------
                    
                    # 捕获所有其他类型的异常，防止程序崩溃
                    print(f"警告：计算第 {i} 行 {period_name} 时发生未知错误: {e}")
                    max_drawdowns.append(np.nan)
            
            # ---------- 步骤3.6：批量赋值 ----------
            
            # 将计算完成的最大回撤列表赋值给DataFrame的新列
            result_df[period_name] = max_drawdowns

        # ==================== 第四步：返回结果 ====================
        
        # 返回添加了最大回撤列的DataFrame
        return result_df
        
    except Exception as e:
        # ==================== 函数级异常处理 ====================
        
        # 捕获整个函数执行过程中的任何异常
        # 这是最后一道防线，确保函数不会因未处理的异常而崩溃
        print(f"错误：计算最大回撤时发生异常 - {e}")
        return None

def calculate_investment_statistics(df):
    """
    计算不同持有期的投资统计指标
    
    本函数基于已计算的收益率和最大回撤数据，为每个时间窗口计算统计指标，
    包括盈利概率、收益率均值/标准差、最大回撤均值/标准差等，用于评估投资风险和收益特征。
    
    参数:
        df (pandas.DataFrame): 包含收益率和最大回撤数据的数据框
    
    返回:
        pandas.DataFrame: 添加了统计指标列的数据框
        
    功能说明:
        1. 对每个时间窗口（1周、1月、3月等）计算投资统计指标
        2. 使用滑动窗口方法，为每一行计算基于该行及之后所有数据的统计值
        3. 计算盈利概率：收益率大于0的比例
        4. 计算收益率的均值和标准差：衡量平均收益和波动性
        5. 计算最大回撤的均值和标准差：衡量平均风险和风险波动
        
    统计指标说明:
        - 盈利概率：在该持有期内，获得正收益的概率
        - 收益率均值：该持有期的平均收益率
        - 收益率标准差：收益率的波动程度（风险指标）
        - 最大回撤均值：该持有期的平均最大回撤
        - 最大回撤标准差：最大回撤的波动程度
    """
    
    # 创建数据框的副本，避免修改原始数据
    result_df = df.copy()

    # ==================== 第一步：定义时间窗口 ====================
    
    # 定义不同持有期对应的交易日天数
    # 与收益率和回撤计算使用相同的时间窗口定义
    periods = {
        '1周': 5,      # 5个交易日
        '1月': 22,     # 约1个月的交易日
        '3月': 66,     # 约3个月的交易日
        '半年': 132,   # 约半年的交易日
        '1年': 242,    # 约1年的交易日
        '2年': 484,    # 约2年的交易日
        '3年': 726     # 约3年的交易日
    }

    # ==================== 第二步：计算各期统计指标 ====================
    
    # 遍历每个时间窗口
    for period_name, days in periods.items():
        
        # 构建列名：对应该时间窗口的收益率和回撤列
        revenue_period = f'持有{period_name}收益率(%)'
        drawdown_period = f'持有{period_name}最大回撤(%)'

        # ---------- 步骤2.1：提取收益率和回撤数据 ----------
        
        # 获取该时间窗口的所有收益率数据（包含NaN）
        revenue_series = result_df[revenue_period]
        
        # 获取该时间窗口的所有最大回撤数据（包含NaN）
        drawdown_series = result_df[drawdown_period]
        
        # 找出有效数据的索引位置（非NaN的行）
        valid_indices = revenue_series.notna()
        
        # 提取有效的收益率和回撤值用于计算
        revenue_nav_values = revenue_series[valid_indices].values
        drawdown_nav_values = drawdown_series[valid_indices].values

        # ---------- 步骤2.2：初始化结果列表 ----------
        
        # 初始化列表用于批量存储计算结果，长度与原始DataFrame相同
        # 默认填充NaN，对于有效数据的行会被计算结果覆盖
        profit_probabilities = [np.nan] * len(result_df)
        mean_values = [np.nan] * len(result_df)
        std_values = [np.nan] * len(result_df)
        drawdown_mean_values = [np.nan] * len(result_df)
        drawdown_std_values = [np.nan] * len(result_df)
        
        # 获取有效数据在原始DataFrame中的实际索引位置
        valid_positions = result_df.index[valid_indices].tolist()
        
        # ---------- 步骤2.3：遍历每一行计算统计指标 ----------
        
        # 对每一行（每个有效数据点）计算统计指标
        # i表示当前行在有效数据中的索引位置
        # actual_idx表示该行在原始DataFrame中的实际索引
        for i in range(len(revenue_nav_values)):
            actual_idx = valid_positions[i]
            # 获取从当前行到最后的所有收益率数据（滑动窗口）
            # 这样可以计算"从当前时点开始投资"的统计特征
            revenue_window_nav = revenue_nav_values[i:]

            # # 调试信息：显示当前计算进度和数据范围
            # print(f"正在计算第：{i}行，取值范围：{days}个交易日")
            # print(f"当前收益率(%)取值数据量：{len(revenue_window_nav)}")
            # print(f"收益率(%)取值预览：{revenue_window_nav}")

            # ---------- 步骤2.3.1：计算盈利概率 ----------
            
            # 盈利概率 = 收益率大于0的次数 / 总次数 × 100%
            # (revenue_window_nav > 0) 返回布尔数组，True表示盈利
            # .sum() 统计True的个数（盈利次数）
            profit_probability = round((revenue_window_nav > 0).sum() / len(revenue_window_nav) * 100, 2)
            
            # 将结果存储到对应的索引位置
            profit_probabilities[actual_idx] = profit_probability

            # ---------- 步骤2.3.2：计算收益率的均值和标准差 ----------
            
            # 检查窗口数据是否有效
            if len(revenue_window_nav) > 0:
                # 计算收益率均值：平均收益水平
                mean_value = round(revenue_window_nav.mean(), 2)
                
                # 计算收益率标准差：收益波动程度（风险指标）
                # 标准差越大，收益波动越大，风险越高
                std_value = round(revenue_window_nav.std(), 2)
            else:
                # 数据为空时，设置默认值
                mean_value = 0
                std_value = 0

            # 将结果存储到对应的索引位置
            mean_values[actual_idx] = mean_value
            std_values[actual_idx] = std_value

            # ---------- 步骤2.3.3：计算最大回撤的均值和标准差 ----------
            
            # 获取从当前行到最后的所有最大回撤数据（滑动窗口）
            drawdown_window_nav = drawdown_nav_values[i:]

            # 检查窗口数据是否有效
            if len(drawdown_window_nav) > 0:
                # 计算最大回撤均值：平均风险水平
                drawdown_mean_value = round(drawdown_window_nav.mean(), 2)
                
                # 计算最大回撤标准差：风险波动程度
                # 标准差越大，风险的不确定性越高
                drawdown_std_value = round(drawdown_window_nav.std(), 2)
            else:
                # 数据为空时，设置默认值
                drawdown_mean_value = 0
                drawdown_std_value = 0

            # 将结果存储到对应的索引位置
            drawdown_mean_values[actual_idx] = drawdown_mean_value
            drawdown_std_values[actual_idx] = drawdown_std_value

            # ---------- 步骤2.3.4：输出计算结果 ----------
            
            # # 打印当前行的所有统计指标，便于调试和监控
            # print(f"持有{period_name}盈利概率为：{profit_probability}")
            # print(f"持有{period_name}收益率均值为：{mean_value}")
            # print(f"持有{period_name}收益率标准差为：{std_value}")
            # print(f"持有{period_name}最大回撤均值为：{drawdown_mean_value}")
            # print(f"持有{period_name}最大回撤标准差为：{drawdown_std_value}")
        
        # ---------- 步骤2.4：批量赋值 ----------
        
        # 一次性将所有计算结果赋值给DataFrame的对应列
        # 这种批量操作比逐行赋值快几十到几百倍
        probability_column = f'持有{period_name}盈利概率'
        mean_column = f'持有{period_name}收益率均值'
        std_column = f'持有{period_name}收益率标准差'
        drawdown_mean_column = f'持有{period_name}最大回撤均值'
        drawdown_std_column = f'持有{period_name}最大回撤标准差'
        
        result_df[probability_column] = profit_probabilities
        result_df[mean_column] = mean_values
        result_df[std_column] = std_values
        result_df[drawdown_mean_column] = drawdown_mean_values
        result_df[drawdown_std_column] = drawdown_std_values

    # ==================== 第三步：返回结果 ====================
    
    # 返回添加了统计指标列的DataFrame
    return result_df

def reorder_columns_by_correlation(df, fund_code=None, fund_name=None):
    """
    DataFrame列重排序函数 - 提升数据可读性和分析效率

    Args:
        df (pd.DataFrame): 包含完整基金分析指标的DataFrame
        fund_code (str, optional): 基金代码，将写入到DataFrame中
        fund_name (str, optional): 基金简称，将插入到基金代码列之后

    Returns:
        pd.DataFrame: 按照逻辑相关性重新排序后的DataFrame
    """
    # 如果提供了基金代码，则添加到DataFrame中
    if fund_code is not None:
        df['基金代码'] = fund_code
    
    # 如果提供了基金简称，则添加到DataFrame中
    if fund_name is not None:
        df['基金简称'] = fund_name
    
    # 定义第一层：基础信息列
    # 这些列提供基金的基本身份和时间信息，是最重要的元数据
    base_columns = [
        '基金代码',    # 基金的唯一标识符
        '基金简称',    # 基金简称
        '净值日期',    # 交易日期，作为时间序列索引
        '单位净值',    # 基金的基本价格单位
        '累计净值',    # 包含分红的累计价格
        '净值异常'     # 数据质量检测标识
    ]

    # 定义第二层：按持有周期分组的分析指标列
    # 每个周期包含7个核心指标，按重要性排列
    periods = {
        '1周': [
            '持有1周收益率(%)',         # 核心收益指标
            '持有1周最大回撤(%)',       # 核心风险指标
            '持有1周盈利概率',          # 概率性指标
            '持有1周收益率均值',        # 期望收益
            '持有1周收益率标准差',      # 收益波动性
            '持有1周最大回撤均值',      # 期望风险
            '持有1周最大回撤标准差'     # 风险波动性
        ],
        '1月': [
            '持有1月收益率(%)', '持有1月最大回撤(%)', '持有1月盈利概率',
            '持有1月收益率均值', '持有1月收益率标准差', '持有1月最大回撤均值', '持有1月最大回撤标准差'
        ],
        '3月': [
            '持有3月收益率(%)', '持有3月最大回撤(%)', '持有3月盈利概率',
            '持有3月收益率均值', '持有3月收益率标准差', '持有3月最大回撤均值', '持有3月最大回撤标准差'
        ],
        '半年': [
            '持有半年收益率(%)', '持有半年最大回撤(%)', '持有半年盈利概率',
            '持有半年收益率均值', '持有半年收益率标准差', '持有半年最大回撤均值', '持有半年最大回撤标准差'
        ],
        '1年': [
            '持有1年收益率(%)', '持有1年最大回撤(%)', '持有1年盈利概率',
            '持有1年收益率均值', '持有1年收益率标准差', '持有1年最大回撤均值', '持有1年最大回撤标准差'
        ],
        '2年': [
            '持有2年收益率(%)', '持有2年最大回撤(%)', '持有2年盈利概率',
            '持有2年收益率均值', '持有2年收益率标准差', '持有2年最大回撤均值', '持有2年最大回撤标准差'
        ],
        '3年': [
            '持有3年收益率(%)', '持有3年最大回撤(%)', '持有3年盈利概率',
            '持有3年收益率均值', '持有3年收益率标准差', '持有3年最大回撤均值', '持有3年最大回撤标准差'
        ]
    }

    # 构建新的列顺序列表
    # 从基础列开始，确保元数据在前
    # 只添加实际存在的基础列
    new_column_order = [col for col in base_columns if col in df.columns]

    # 按时间周期顺序添加分析指标列
    # 外层循环：遍历每个时间周期（从短到长）
    for period_name, columns in periods.items():
        # 内层循环：遍历该周期的所有指标列
        for col in columns:
            # 检查列是否存在，避免引用不存在的列
            if col in df.columns:
                new_column_order.append(col)

    # 处理第三层：其他未分类的列
    # 这确保所有列都被包含，不会丢失任何数据
    remaining_columns = [col for col in df.columns if col not in new_column_order]
    new_column_order.extend(remaining_columns)

    # 按照新顺序重新构建DataFrame
    # 这一步实际上不移动数据，只是改变列的显示顺序
    result_df = df[new_column_order]

    return result_df

def process_fund_data(symbol: str, fund_name: str = None) -> pd.DataFrame:
    """
    处理基金数据的完整流程，包括获取数据、计算收益率、回撤、统计指标和列重排序
    
    Args:
        symbol: 基金代码
        fund_name: 基金简称（可选）
        
    Returns:
        pd.DataFrame: 处理完成的基金数据DataFrame
    """
    df = get_combined_fund_data(symbol)
    df = calculate_period_returns(df)
    df = calculate_max_drawdown(df)
    df = calculate_investment_statistics(df)
    df = reorder_columns_by_correlation(df, fund_code=symbol, fund_name=fund_name)
    
    return df

if __name__ == "__main__":
    # 开始计时
    start_time = time.time()
    
    symbol = "006401"
    fund_name="先锋量化优选混合A"
    df = process_fund_data(symbol, fund_name)
    
    # 保存数据到本地CSV文件
    output_file = "fund_data_710001_测试.csv"
    df.to_csv(output_file, index=False, encoding='utf-8-sig')
    print(f"\n数据已保存到: {output_file}")
    
    # 结束计时并输出耗时
    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"总耗时: {elapsed_time:.2f} 秒")